// -*-mode:c++; coding:utf-8-*-

#ifndef _COMPRESSOR_ZLIB_HPP_
#define _COMPRESSOR_ZLIB_HPP_

#include <unistd.h>

#include "wrapper.hpp"

namespace {
#include <zlib.h>
}

namespace xbase
{
	namespace compressor
	{
		struct zlib
		{
			// return - zlen if success, or nagetive number
			template<typename Buffer>
			static inline
			ssize_t compress(Buffer &buffer,
					      const boost::asio::const_buffer &data) {
				const uint32_t &in_size =
					boost::asio::buffer_size(data);
				const size_t max_out_size = compressBound(in_size) + sizeof(uint32_t)/*for header length*/;
				boost::asio::mutable_buffer out = buffer.prepare(max_out_size);
				integer_wrapper<uint32_t, kb::network_byteorder> *in_size_wrapper = reinterpret_cast<integer_wrapper<uint32_t, kb::network_byteorder> *>(boost::asio::buffer_cast<char*>(out));
				in_size_wrapper->set(in_size);
				out = out + in_size_wrapper->bytes();

				uLongf real_out_size = boost::asio::buffer_size(out);
				const int ret = compress2(boost::asio::buffer_cast<unsigned char*>(out),
							  &real_out_size,
							  boost::asio::buffer_cast<const unsigned char*>(data),
							  in_size,
							  1); // 1 level for max speed
				if(ret != Z_OK)
				{
					return -1;
				}

				real_out_size += in_size_wrapper->bytes();
				buffer.commit(real_out_size);
				return real_out_size;
			}

			// return - zlen if success, or nagetive number
			template<typename Buffer>
			static inline
			ssize_t decompress(Buffer &buffer,
						const boost::asio::const_buffer &data) {
				if(boost::asio::buffer_size(data) < sizeof(uint32_t))
				{
					return -1;
				}
				
				const integer_wrapper<uint32_t, kb::network_byteorder> *expect_out_size_wrapper = reinterpret_cast<const integer_wrapper<uint32_t, kb::network_byteorder> *>(boost::asio::buffer_cast<const char *>(data));
				boost::asio::mutable_buffer out = buffer.prepare(expect_out_size_wrapper->get());

				const boost::asio::const_buffer real_data =
					data + expect_out_size_wrapper->bytes();
				uLongf real_out_size = boost::asio::buffer_size(out);
				const int ret = uncompress(boost::asio::buffer_cast<unsigned char*>(out),
							   &real_out_size,
							   boost::asio::buffer_cast<const unsigned char*>(real_data),
							   boost::asio::buffer_size(real_data));
				if(ret != Z_OK)
				{
					return -1;
				}
				
				buffer.commit(real_out_size);
				return real_out_size;
			}
		};

		// return - zlen if success, or nagetive number
		//          -1 for input data error;
		//          -2 for output buffer too small
		template<>
		inline
		ssize_t zlib::decompress(const boost::asio::mutable_buffer &buffer,
					      const boost::asio::const_buffer &data)
		{
			if(boost::asio::buffer_size(data) < sizeof(uint32_t))
			{
				return -1;
			}
				
			const integer_wrapper<uint32_t, kb::network_byteorder> *expect_out_size_wrapper = reinterpret_cast<const integer_wrapper<uint32_t, kb::network_byteorder> *>(boost::asio::buffer_cast<const char *>(data));
			
			if(boost::asio::buffer_size(buffer)
			   < expect_out_size_wrapper->get()) // not enough
			{
				return -1;
			}

			const boost::asio::const_buffer real_data =
				data + expect_out_size_wrapper->bytes();
			uLongf real_out_size = boost::asio::buffer_size(buffer);
			Bytef *dest = boost::asio::buffer_cast<unsigned char*>(buffer);
			const Bytef *src = boost::asio::buffer_cast<const unsigned char*>(real_data);
			const int ret = uncompress(dest,
						   &real_out_size,
						   src,
						   static_cast<uLong>(boost::asio::buffer_size(real_data)));
			if(ret != Z_OK)
			{
				return -1;
			}
			return real_out_size;
		}
	} // namespace compressor

} // namespace xbase

#endif	// _COMPRESSOR_ZLIB_HPP_
